<!--
 *                                |~~~~~~~|
 *                                |       |
 *                                |       |
 *                                |       |
 *                                |       |
 *                                |       |
 *     |~.\\\_\~~~~~~~~~~~~~~xx~~~         ~~~~~~~~~~~~~~~~~~~~~/_//;~|
 *     |  \  o \_         ,XXXXX),                         _..-~ o /  |
 *     |    ~~\  ~-.     XXXXX`)))),                 _.--~~   .-~~~   |
 *      ~~~~~~~`\   ~\~~~XXX' _/ ';))     |~~~~~~..-~     _.-~ ~~~~~~~
 *               `\   ~~--`_\~\, ;;;\)__.---.~~~      _.-~
 *                 ~-.       `:;;/;; \          _..-~~
 *                    ~-._      `''        /-~-~
 *                        `\              /  /
 *                          |         ,   | |
 *                           |  '        /  |
 *                            \/;          |
 *                             ;;          |
 *                             `;   .       |
 *                             |~~~-----.....|
 *                            | \             \
 *                           | /\~~--...__    |
 *                           (|  `\       __-\|
 *                           ||    \_   /~    |
 *                           |)     \~-'      |
 *                            |      | \      '
 *                            |      |  \    :
 *                             \     |  |    |
 *                              |    )  (    )
 *                               \  /;  /\  |
 *                               |    |/   |
 *                               |    |   |
 *                                \  .'  ||
 *                                |  |  | |
 *                                (  | |  |
 *                                |   \ \ |
 *                                || o `.)|
 *                                |`\\) |
 *                                |       |
 *                                |       |
 -->

<template>
  <el-container id="container">
    <div id="mountNode"></div>
  </el-container>
</template>

<script>
import G6 from '@antv/g6'
import {
  company
} from '@/assets/js/company'
export default {
  name: 'Home',
  data () {
    return {
      // G6实例
      graph: null,
      // 用来记录已经点击过的节点
      currentNode: [],
      // data
      g6Obj: {}
    }
  },
  mounted () {
    this.formatData(company)
    // 配合State使用 mouseenter mouseleave
    this.graph.on('edge:mouseenter', (evt) => {
      const { item } = evt
      this.graph.setItemState(item, 'hover', true)
      this.mouseEnterLabelStyle(item)
    })

    this.graph.on('edge:mouseleave', (evt) => {
      const { item } = evt
      this.graph.setItemState(item, 'hover', false)
      this.mouseLeaveLabelStyle(item)
    })
    // 监听鼠标进入节点
    this.graph.on('node:mouseenter', (e) => {
      const item = e.item
      // 设置目标节点的 hover 状态 为 true
      this.graph.setItemState(item, 'hover', true)
      // this.nodeHightLight(item)
    })
    // 监听鼠标离开节点
    this.graph.on('node:mouseleave', (e) => {
      const nodeItem = e.item
      // 设置目标节点的 hover 状态 false
      this.graph.setItemState(nodeItem, 'hover', false)
      this.clearHightLight()
    })
    // 单击节点
    this.graph.on('node:click', (ev) => {
      const id = ev.item.get('id')
      console.log(id)
    })
    // 双击节点
    this.graph.on('node:dblclick', (ev) => {
      const id = ev.item.get('id')
      // 把其余节点全部删除掉
      if (this.currentNode.indexOf(id) === -1) {
        this.currentNode.push(id)
      }
      this.g6Obj.nodes.forEach(item => {
        const thisId = item.id
        if (this.currentNode.indexOf(thisId) === (-1)) {
          const item = this.graph.findById(thisId)
          // this.graph.hideItem(item) // 隐藏节点
          this.graph.removeItem(item) // 删除节点
        }
      })
      this.addChildrenNodes(id)
    })
  },
  methods: {
    /**
     * 图谱初始化
     * @date 2021-07-20
     * @param {Object} data
     */
    initGraph (data) {
      // 提示框
      const tooltip = new G6.Tooltip({
        offsetX: 10,
        offsetY: 10,
        // 允许出现 tooltip 的 item 类型
        itemTypes: ['node'],
        // 自定义 tooltip 内容
        getContent: (e) => {
          const label = e.item.getModel().data.label
          const outDiv = document.createElement('div')
          outDiv.style.width = 'fit-content'
          outDiv.innerHTML = `<span>${label}</span>`
          return outDiv
        }
      })
      // 工具栏
      // const toolbar = new G6.ToolBar()
      this.graph = new G6.Graph({
        container: 'mountNode',
        fitCenter: true, // 自动居中
        plugins: [tooltip],
        modes: {
          // [drag-canvas  拖拽画布]、[drag-node  拖拽节点]、[zoom-canvas 画布滚轮缩放大小]、[activate-relations 鼠标划到节点显示关系节点]
          default: [
            'drag-node',
            {
              type: 'drag-canvas',
              enableOptimize: false // 拖拽画布时隐藏文字
            },
            {
              type: 'zoom-canvas',
              enableOptimize: false, // 缩放画布时隐藏文字
              minZoom: 0.5,
              maxZoom: 3
            }
          ]
        },
        // force 布局经典的力导向布局方法，与 d3 的力导向布局方法相对应。其属性也与 d3.js 的力导布局参数相对应
        layout: {
          type: 'force',
          linkDistance: 200, // 边长度
          nodeStrength: 30, // 节点作用力，正数代表节点之间的引力作用，负数代表节点之间的斥力作用
          edgeStrength: 0.3, // 边的作用力，范围是 0 到 1，默认根据节点的出入度自适应
          collideStrength: 0.1, // 防止重叠的力强度
          nodeSize: 80, // 节点大小
          nodeSpacing: 30, // 间距
          alpha: 0.8, // 当前的迭代收敛阈值
          alphaDecay: 0.028, // 迭代阈值的衰减率。范围 [0, 1]。0.028 对应迭代数为 300
          alphaMin: 0.01, // 停止迭代的阈值
          forceSimulation: null, // 自定义 force 方法，若不指定，则使用 d3.js 的方法
          preventOverlap: true, // 是否防止重叠
          condense: true,
          onTick: () => { // 每一次迭代的回调函数
          },
          onLayoutEnd: () => { // 布局完成后的回调函数
            this.graph.refreshPositions()
          }
        },
        animate: true, // 添加动画
        defaultNode: {
          size: 80,
          labelCfg: {
            position: 'center',
            autoRotate: true,
            offset: 0,
            style: {
              fill: '#ffffff'
            }
          }
        }
      })
      this.graph.data(data) // 加载数据
      this.graph.render() // 重新渲染图谱
      this.handleLineOverlap(data)
    },
    /**
     * 解决多重关系重叠问题
     * @date 2021-07-21
     * @param {any} data
     * @returns {any}
     */
    handleLineOverlap (data) {
      const offsetDiff = 25 // 两条平行边的之间的距离
      const multiEdgeType = 'quadratic' // 两节点之间若存在多条边时，该边的类型
      const singleEdgeType = 'line' // 两节点之间仅有一条边时，该边的类型
      const loopEdgeType = 'undefined' // 若一条边的起点和终点是同一个节点（自环边），该边的类型，默认为 undefined，即不改变这种边的类型
      G6.Util.processParallelEdges(data.edges, offsetDiff, multiEdgeType, singleEdgeType, loopEdgeType)
    },
    /**
     * 初始化数据格式 eg: {nodes: [], edges: []}
     * @date 2021-07-20
     * @param {Object} data
     * @returns {Null}
     */
    formatData (data) {
      const g6Obj = {
        nodes: [],
        edges: []
      }
      const nodes = data.nodes
      const lines = data.edges
      nodes.forEach(item => {
        const obj = {
          id: String(item.id),
          data: item,
          style: {
            fill: '#4EA2F0',
            stroke: '#2994EE'
          }
        }
        switch (item.type) {
          case 1:
            if (item.name.length > 6) {
              obj.label = item.name.slice(0, 6) + '\n' + item.name.slice(6, 12) + '\n' + item.name.slice(12)
            } else {
              obj.label = item.name
            }
            obj.size = 80
            break
          case 5:
            if (item.entName.length > 6) {
              obj.label = item.entName.slice(0, 6) + '\n' + item.entName.slice(6, 12) + '\n' + item.entName.slice(12)
            } else {
              obj.label = item.entName
            }
            obj.size = 80
            break
        }
        if (obj.id === '20') {
          this.currentNode.push(obj.id)
          obj.style.fill = '#FF9E00'
        }
        g6Obj.nodes.push(obj)
      })
      lines.forEach(item => {
        const obj = {
          source: String(item.from),
          target: String(item.to),
          label: item.label,
          style: { // 线条样式
            endArrow: {
              path: 'M 0,0 L 8,4 L 8,-4 Z'
            }
          },
          labelCfg: { // 标签文本
            style: {
              fill: '#c1c1c1'
            }
          },
          stateStyles: { // 处于状态下样式
            hover: {
              lineWidth: 2
            }
          }
        }
        switch (item.type) {
          case 11:
            obj.style.endArrow.fill = '#f47920'
            obj.style.endArrow.stroke = '#f47920'
            obj.stateStyles.hover.stroke = '#f47920'
            break
          case 12:
            obj.style.endArrow.fill = '#2e3a1f'
            obj.style.endArrow.stroke = '#2e3a1f'
            obj.stateStyles.hover.stroke = '#2e3a1f'
            break
          case 13:
            obj.style.endArrow.fill = '#ef5b9c'
            obj.style.endArrow.stroke = '#ef5b9c'
            obj.stateStyles.hover.stroke = '#ef5b9c'
            break
          case 15:
            obj.style.endArrow.fill = '#d71345'
            obj.style.endArrow.stroke = '#d71345'
            obj.stateStyles.hover.stroke = '#d71345'
            break
          default:
            obj.style.endArrow.fill = '#ed1941'
            obj.style.endArrow.stroke = '#ed1941'
            obj.stateStyles.hover.stroke = '#ed1941'
            break
        }
        g6Obj.edges.push(obj)
      })
      this.g6Obj = g6Obj
      this.initGraph(g6Obj)
    },
    /**
     * 添加子节点
     * @date 2021-07-20
     * @param {String} id
     */
    addChildrenNodes (id) {
      const nodeLen = Math.round(Math.random() * 10)
      for (let i = 0; i < nodeLen; i++) {
        const newId = Math.round(Math.random() * 10000 + 10000)
        let label = `节点${newId}`
        if (label.length > 6) {
          label = label.slice(0, 6) + '\n' + label.slice(6, 12) + '\n' + label.slice(12)
        }
        this.graph.add('node', {
          id: String(newId),
          label: label,
          data: {
            id: String(newId),
            label: `节点${newId}`
          },
          style: {
            fill: '#4EA2F0',
            stroke: '#2994EE'
          }
        })
        this.g6Obj.nodes.push({
          id: String(newId),
          label: label,
          data: {
            id: String(newId),
            label: `节点${newId}`
          }
        })
        const color = this.getRandomColor()
        this.graph.add('edge', {
          source: String(id),
          target: String(newId),
          label: `线段${newId}`,
          style: { // 线条样式
            stroke: color,
            endArrow: {
              path: 'M 0,0 L 8,4 L 8,-4 Z',
              fill: color,
              stroke: color
            }
          },
          labelCfg: { // 标签文本
            style: {
              fill: '#c1c1c1'
            }
          },
          stateStyles: { // 处于状态下样式
            hover: {
              stroke: color,
              lineWidth: 2
            }
          }
        })
      }
      this.graph.updateLayout({
        type: 'force',
        linkDistance: 200, // 边长度
        nodeStrength: 30, // 节点作用力，正数代表节点之间的引力作用，负数代表节点之间的斥力作用
        edgeStrength: 0.3, // 边的作用力，范围是 0 到 1，默认根据节点的出入度自适应
        collideStrength: 0.1, // 防止重叠的力强度
        nodeSize: 80, // 节点大小
        nodeSpacing: 30, // 间距
        alpha: 0.8, // 当前的迭代收敛阈值
        alphaDecay: 0.028, // 迭代阈值的衰减率。范围 [0, 1]。0.028 对应迭代数为 300
        alphaMin: 0.01, // 停止迭代的阈值
        forceSimulation: null, // 自定义 force 方法，若不指定，则使用 d3.js 的方法
        preventOverlap: true, // 是否防止重叠
        onTick: () => { // 每一次迭代的回调函数
        },
        onLayoutEnd: () => { // 布局完成后的回调函数
          this.graph.render()
        }
      })
    },
    /**
     * 随机获取颜色
     * @date 2021-07-20
     * @returns {String} 样式
     */
    getRandomColor () {
      return '#' + ('00000' + ((Math.random() * 16777215 + 0.5) >> 0).toString(16)).slice(-6)
    },
    /**
     * 节点高亮
     * @date 2021-07-21
     * @param {Object} item
     */
    nodeHightLight (item) {
      const _this = this
      this.graph.setAutoPaint(false)
      this.graph.getNodes().forEach(function (node) {
        _this.graph.clearItemStates(node)
        _this.graph.setItemState(node, 'dark', true)
      })
      this.graph.setItemState(item, 'dark', false)
      this.graph.setItemState(item, 'highlight', true) // 节点高亮
      this.graph.getEdges().forEach(function (edge) {
        if (edge.getSource() === item) {
          _this.graph.setItemState(edge.getTarget(), 'dark', false)
          _this.graph.setItemState(edge.getTarget(), 'highlight', true)
          _this.graph.setItemState(edge, 'highlight', true) // 线条高亮
          edge.toFront()
        } else if (edge.getTarget() === item) {
          _this.graph.setItemState(edge.getSource(), 'dark', false)
          _this.graph.setItemState(edge.getSource(), 'highlight', true)
          _this.graph.setItemState(edge, 'highlight', true) // 线条高亮
          edge.toFront()
        } else {
          _this.graph.setItemState(edge, 'highlight', false)
        }
      })
      this.graph.paint()
      this.graph.setAutoPaint(true)
    },
    /**
     * 清除页面状态
     * @date 2021-07-21
     */
    clearHightLight () {
      const _this = this
      this.graph.setAutoPaint(false)
      this.graph.getNodes().forEach(function (node) {
        _this.graph.clearItemStates(node)
      })
      this.graph.getEdges().forEach(function (edge) {
        _this.graph.clearItemStates(edge)
      })
      this.graph.paint()
      this.graph.setAutoPaint(true)
    },
    /**
     * 鼠标移入线条改变文本的样式
     * @date 2021-07-21
     * @param {Object} item
     */
    mouseEnterLabelStyle (item) {
      const model = {
        labelCfg: {
          style: {
            fill: item.get('styles').hover.stroke,
            fontSize: 16
          }
        }
      }
      this.graph.updateItem(item, model)
    },
    /**
     * 鼠标移出线条恢复样式
     * @date 2021-07-21
     * @param {Object} item
     */
    mouseLeaveLabelStyle (item) {
      const model = {
        labelCfg: {
          style: {
            fill: '#c1c1c1',
            fontSize: 12
          }
        }
      }
      this.graph.updateItem(item, model)
    }
  }
}
</script>

<style lang="scss" scoped>
#mountNode {
  width: 100%;
  height: 100%;
}
</style>
